/**
 * Copyright 2006 gworks.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 */
package au.com.gworks.jump.io;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import au.com.gworks.jump.system.ApplicationManager;


/**
 * <p>
 * Attributes/meta-data associated with a resource within the repository.
 * </p>
 * 
 * <p>
 * Why: 
 * <ol>
 * <li> often views and clients need more than just the full paths to a resource.
 * </ol>
 * </p>
 */
public class ResourceAttributes implements Serializable {
	
	/**
	 * If the internal state of this class ever changes in such a way that it can't be defaulted,
	 * then the {@link #serialVersionUID} should be incremented to ensure serialized instances cleanly fail.  
	 */
	private static final long serialVersionUID = 1;

	public static final String NULL_UUID = "";

	public static final Integer NULL_HEAD_REVISION = new Integer(-1);
	
	public static final long NULL_SIZE = 0;

	public static final Properties NULL_PROPERTIES = new Properties();
	
	private final String name;
	private final String parentPath;
	private final Integer revision; 
	private final long lastModified;
	private final String lastUpdater;
    private final Integer lastChangedRevision; 
	private final long size;
	private final String uuid;
    private final Properties properties = new Properties();
    private final Set modifiedProperties = new HashSet();
    private final Set deletedProperties = new HashSet();

    /**
     * Creates a ResourceAttributes that has everything, including the Revision at which the Resource was last changed. 
     */
    public ResourceAttributes(String parentPath, String name, 
            String uuid, long lastModified, String lastUpdater,
            long size, Integer revision, Properties properties, Integer lastChangedRevision) {
        this.parentPath = parentPath;
        this.name = name;
        this.revision = revision;
        this.uuid = uuid;
        this.lastModified = lastModified;
        this.lastUpdater = lastUpdater;
        this.size = size;
        this.properties.putAll(properties);
        this.lastChangedRevision = lastChangedRevision;
    }
            
    /**
	 * Creates a ResourceAttributes which is a copy of the supplied ResourceAttributes
	 * in all respects except for size. 
	 */
	public ResourceAttributes(ResourceAttributes copy, long size) {
		// TODO [refactor in DB proj] Supply the Principal and update last Modified as this represents an actual change to content. 
		this(copy.parentPath, copy.name, copy.uuid, copy.lastModified, copy.lastUpdater, size, copy.revision, copy.properties, NULL_HEAD_REVISION);
        this.deletedProperties.addAll(copy.deletedProperties);
	}
	
	/**
	 * Creates a ResourceAttributes which is a copy of the supplied ResourceAttributes 
	 * in all respects except that it's path within the repository has been changed.
	 */
	public ResourceAttributes(ResourceAttributes copy, String parentPath, String name, String uuid) {
		this(parentPath, name, uuid, copy.lastModified, copy.lastUpdater, copy.size, copy.revision, copy.properties, NULL_HEAD_REVISION);
        this.deletedProperties.addAll(copy.deletedProperties);
	}
	
	/**
	 * Returns the unique Name for this Resource within its containing Folder.
	 */
	public String getName() {
		return this.name;
	}

	/**
	 * Returns the Revision of this Resource. 
	 */
	public Integer getRevision() {
		return this.revision;
	}
	
	/**
	 * @return a fully qualified path of the resource (ie. parent path + name)
	 */
	public String getAbsolutePath() {
		String ret = parentPath;
		if (!ret.endsWith("/"))
			ret += "/";
		ret += name;
		return ret;
	}

	/**
	 * Returns a unique identifier for this Resource.
	 */
	public String getUuid() {
		return this.uuid;
	}
	
	/**
	 * @return false if the mime type of the resource is considreed binary
	 * 			from the resource's properties perspective.
	 * @return true of the associated mime type is either null (ie. unknown) or
	 * has "text" or "xml" inside the mime type.
	 * 
	 * @see ApplicationManager#mimeTypeQuerier
	 * @see ApplicationManager.MimeTypeQuerier
	 */
	public boolean isTextBasedMimeContentType() {
		final String mimeOrContentType = ApplicationManager.mimeTypeQuerier.getMimeType( name );
		if (mimeOrContentType == null) {
			return true;
		}
		if ((mimeOrContentType.indexOf("text") > -1) || (mimeOrContentType.indexOf("xml") > -1)) {
			return true;
		}
		return false;
	}

	public long getSize() {
		return size;
	}

    // Methods that provide access to the user (dead) properties.
    
    /**
     * Returns the value of the specified user property.
     */
    public String getProperty(String propName) {
        return this.properties.getProperty(propName);
    }
    
    /**
     * Returns the names of all the user properties contained by this ResourceAttributes.
     */
    public String[] getPropertyNames() {
        return (String[]) this.properties.keySet().toArray(new String[0]);
    }
    
    /**
     * Returns an unmodifiable Map of the Resource properties.
     * <p>
     * In the case of a Resource delta this would only include the modified properties.
     * <p>
     * @see #getDeletedProperties 
     * @see #getModifiedProperties 
     */
    public Map getProperties() {
        return Collections.unmodifiableMap(new HashMap(this.properties));
    }
    
    /**
     * Returns the number of user properties contained by this ResourceAttributes.
     */
    public int getPropertyCount() {
        return this.properties.size();
    }

    /**
     * Sets the value for the supplied user property.
     */
    public void setProperty(String propName, String propValue) {
        this.modifiedProperties.add(propName);
        this.properties.setProperty(propName, propValue);
        this.deletedProperties.remove(propName);
    }
    
    /**
     * Removes the supplied user property from this ResourceAttributes.
     */
    public void removeProperty(String propName) {
        this.deletedProperties.add(propName);
        this.properties.remove(propName);
        this.modifiedProperties.remove(propName);
    }

    /**
     * Returns an unmodifiable Set of those properties that have been added/updated in this ResourceAttributes.
     * 
     * @see #getDeletedProperties 
     * @see #getProperties 
     */
    public Set getModifiedProperties() {
        return Collections.unmodifiableSet(this.modifiedProperties);
    }
    
    /**
     * Returns an unmodifiable Set of those properties that have been removed in this ResourceAttributes.
     * 
     * @see #getProperties 
     * @see #getModifiedProperties 
     */
    public Set getDeletedProperties() {
        return Collections.unmodifiableSet(this.deletedProperties);
    }
    
    // Methods that provide access to the system (live) properties.
    
	public String getLastUpdater() {
		return lastUpdater;
	}

	public long getLastModified() {
		return lastModified;
	}

    /**
     * Revision at which this Resou4rce was last changed.
     */
    public Integer getLastChangedRevision() {
        return this.lastChangedRevision;
    }
    
	public String getParentPath() {
		return parentPath;
	}

	// Object methods
	
	public String toString() {
		return "[Doc:" + getAbsolutePath() + ",r(" + getRevision() + ") ,lm=" + lastModified + ",lu=" + lastUpdater + "]";
	}

	public boolean equals(Object other) {
		if (!(other instanceof ResourceAttributes)) {
			return false;
		}
		final ResourceAttributes otherDoc = (ResourceAttributes) other;
		boolean ret = getAbsolutePath().equals(otherDoc.getAbsolutePath());
		if (ret) {
            ret = getRevision().equals(otherDoc.getRevision());
        }
		return ret;
	}

	public int hashCode() {
		return getAbsolutePath().hashCode();
	}
	
	/**
	 * @return true if the resource attributes as a defined property that considered
	 * to be a binary mime type.
	 * 
	 * @see ApplicationManager#getResourceMimeTypeProperty()
	 * @see ApplicationManager#getResourceBinaryMimeTypeValue()
	 */
	public boolean hasBinaryProperty() {
		Map props = this.getProperties();
		String mimeTypeProp = ApplicationManager.getResourceMimeTypeProperty();
		String binaryMimeType = ApplicationManager.getResourceBinaryMimeTypeValue();
		String mimeVal = (String) props.get(mimeTypeProp);
		return (binaryMimeType.equals(mimeVal));
	}
}